package ui.swing.combobox;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;

import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;

@SuppressWarnings( { "unchecked", "serial" })
public class ComboBoxDemo extends JFrame {

	public ComboBoxDemo() {
		super("ComboBoxes [Compare Cars]");
		// getContentPane().setLayout(new BorderLayout());

		Vector cars = new Vector();
		Car maxima = new Car("Maxima", "Nissan", new ImageIcon("maxima.gif"));
		maxima.addTrim("GXE", 21499, 19658, "3.0L V6 190-hp");
		maxima.addTrim("SE", 23499, 21118, "3.0L V6 190-hp");
		maxima.addTrim("GLE", 26899, 24174, "3.0L V6 190-hp");
		cars.addElement(maxima);
		Car accord = new Car("Accord", "Honda", new ImageIcon("accord.gif"));
		accord.addTrim("LX Sedan", 21700, 19303, "3.0L V6 200-hp");
		accord.addTrim("EX Sedan", 24300, 21614, "3.0L V6 200-hp");
		cars.addElement(accord);
		Car camry = new Car("Camry", "Toyota", new ImageIcon("camry.gif"));
		camry.addTrim("LE V6", 21888, 19163, "3.0L V6 194-hp");
		camry.addTrim("XLE V6", 24998, 21884, "3.0L V6 194-hp");
		cars.addElement(camry);
		Car lumina = new Car("Lumina", "Chevrolet", new ImageIcon("lumina.gif"));
		lumina.addTrim("LS", 19920, 18227, "3.1L V6 160-hp");
		lumina.addTrim("LTZ", 20360, 18629, "3.8L V6 200-hp");
		cars.addElement(lumina);
		Car taurus = new Car("Taurus", "Ford", new ImageIcon("taurus.gif"));
		taurus.addTrim("LS", 17445, 16110, "3.0L V6 145-hp");
		taurus.addTrim("SE", 18445, 16826, "3.0L V6 145-hp");
		taurus.addTrim("SHO", 29000, 26220, "3.4L V8 235-hp");
		cars.addElement(taurus);
		Car passat = new Car("Passat", "Volkswagen",
				new ImageIcon("passat.gif"));
		passat.addTrim("GLS V6", 23190, 20855, "2.8L V6 190-hp");
		passat.addTrim("GLX", 26250, 23589, "2.8L V6 190-hp");
		cars.addElement(passat);

		getContentPane().setLayout(new GridLayout(1, 2, 5, 5));
		CarPanel pl = new CarPanel("Base Model", cars);
		getContentPane().add(pl);
		CarPanel pr = new CarPanel("Compare to", cars);
		getContentPane().add(pr);

		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		pl.selectCar(maxima);
		pr.selectCar(accord);
		setResizable(false);
		pack();
		setVisible(true);
	}

	public static void main(String[] args) {
		new ComboBoxDemo();
	}
}

@SuppressWarnings( { "unchecked" })
class Car {
	protected String m_name;
	protected String m_manufacturer;
	protected Icon m_img;
	protected Vector m_trims;

	public Car(String name, String manufacturer, Icon img) {
		m_name = name;
		m_manufacturer = manufacturer;
		m_img = img;
		m_trims = new Vector();
	}

	public void addTrim(String name, int MSRP, int invoice, String engine) {
		Trim trim = new Trim(this, name, MSRP, invoice, engine);
		// m_trims.add(trim);
		m_trims.addElement(trim);
	}

	public String getM_manufacturer() {
		return m_manufacturer;
	}

	public Icon getIcon() {
		return m_img;
	}

	public Vector getTrims() {
		return m_trims;
	}

	public String toString() {
		return m_name;
	}
}

class Trim {
	protected Car m_parent;
	protected String m_name;
	protected int m_MSRP;
	protected int m_invoice;
	protected String m_engine;

	public Trim(Car parent, String name, int MSRP, int invoice, String engine) {
		m_parent = parent;
		m_name = name;
		m_MSRP = MSRP;
		m_invoice = invoice;
		m_engine = engine;
	}

	public Car getCar() {
		return m_parent;
	}

	public String getName() {
		return m_name;
	}

	public int getMSRP() {
		return m_MSRP;
	}

	public int getInvoice() {
		return m_invoice;
	}

	public String getEngine() {
		return m_engine;
	}

	public String toString() {
		return m_name;
	}
}

@SuppressWarnings( { "unchecked", "serial" })
class CarPanel extends JPanel {
	protected JComboBox m_cbCars;
	protected JLabel m_txtModel;
	//protected JComboBox m_cbTrims;
	protected JLabel m_lblImg;
	protected JLabel m_lblMSRP;
	protected JLabel m_lblInvoice;
	protected JLabel m_lblEngine;

	public CarPanel(String title, Vector cars) {
		super();
		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		setBorder(new TitledBorder(new EtchedBorder(), title));

		JPanel p = new JPanel();
		m_txtModel = new JLabel("");
		m_txtModel.setForeground(Color.black);
		p.add(m_txtModel);
		add(p);

		// Car Panel, now m_cbCars will show model names along with icons
		p = new JPanel(); // FlowLayout
		p.add(new JLabel("Car:"));
		// *****
		CarComboBoxModel model = new CarComboBoxModel(cars);
		m_cbCars = new JComboBox(model);
		m_cbCars.setRenderer(new IconComboRenderer());
		// *****
		ActionListener lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				ListData data = (ListData) m_cbCars.getSelectedItem();
				Object obj = data.getObject();
				if (obj instanceof Trim)
					showTrim((Trim) obj);
			}
		};
		// *****
		m_cbCars.addActionListener(lst);
		p.add(m_cbCars);
		add(p);

		/*// Trim Panel
		p = new JPanel();
		p.add(new JLabel("Trim:"));
		m_cbTrims = new JComboBox();
		lst = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				Trim trim = (Trim) m_cbTrims.getSelectedItem();
				if (trim != null) {
					showTrim(trim);
				}
			}
		};
		m_cbTrims.addActionListener(lst);
		p.add(m_cbTrims);
		add(p);*/

		// Image
		p = new JPanel();
		m_lblImg = new JLabel();
		m_lblImg.setHorizontalAlignment(JLabel.CENTER);
		m_lblImg.setPreferredSize(new Dimension(140, 80));
		m_lblImg.setBorder(new BevelBorder(BevelBorder.LOWERED));
		p.add(m_lblImg);
		add(p);

		p = new JPanel();
		p.setLayout(new GridLayout(3, 2, 10, 5));
		p.add(new JLabel("MSRP:"));
		m_lblMSRP = new JLabel();
		p.add(m_lblMSRP);
		p.add(new JLabel("Invoice:"));
		m_lblInvoice = new JLabel();
		p.add(m_lblInvoice);
		p.add(new JLabel("Engine:"));
		m_lblEngine = new JLabel();
		p.add(m_lblEngine);
		add(p);
	}

	public void selectCar(Car car) {
		m_cbCars.setSelectedItem(car); // 要fire不少Event，其中ActionEvent被监听到
	}

	/*public void showCar(Car car) {
		m_lblImg.setIcon(car.getIcon());
		if (m_cbTrims.getItemCount() > 0)
			m_cbTrims.removeAllItems();
		Vector v = car.getTrims();
		for (int k = 0; k < v.size(); k++)
			m_cbTrims.addItem(v.elementAt(k)); // 放置第一个Item时，DefaultComboBoxModel会调用setSelectedItem(...)，所以会产生一系列事件，其中ActionEvent会被监听到
		m_cbTrims.grabFocus();
	}*/

	public void showTrim(Trim trim) {
		m_lblMSRP.setText("$" + trim.getMSRP());
		m_lblInvoice.setText("$" + trim.getInvoice());
		m_lblEngine.setText(trim.getEngine());
	}
}

class CarComboBoxModel extends DefaultComboBoxModel {
	private static final long serialVersionUID = 7195293986156528676L;
	public static final ImageIcon ICON_CAR = new ImageIcon(ClassLoader.getSystemResource("resources/images/red.gif"));
	public static final ImageIcon ICON_TRIM = new ImageIcon(ClassLoader.getSystemResource("resources/images/green.gif"));

	public CarComboBoxModel(Vector cars) {
		for (int k = 0; k < cars.size(); k++) {
			Car car = (Car) cars.elementAt(k);
			addElement(new ListData(ICON_CAR, 0, false, car));
			Vector v = car.getTrims();
			for (int i = 0; i < v.size(); i++) {
				Trim trim = (Trim) v.elementAt(i);
				addElement(new ListData(ICON_TRIM, 1, true, trim));
			}
		}
	}

	// This method only allows trims to be selected
	public void setSelectedItem(Object item) {
		if (item instanceof ListData) {
			ListData ldata = (ListData) item;
			if (!ldata.isSelectable()) {
				Object newItem = null;
				int index = getIndexOf(item);
				for (int k = index + 1; k < getSize(); k++) {
					Object item1 = getElementAt(k);
					if (item1 instanceof ListData) {
						ListData ldata1 = (ListData) item1;
						if (!ldata1.isSelectable())
							continue;
					}
					newItem = item1;
					break;
				}
				if (newItem == null)
					return; // Selection failed
				item = newItem;
			}
		}
		super.setSelectedItem(item);
	}
}

// Encapsulates combo box data and rendering information
class ListData {
	protected Icon m_icon;
	protected int m_index;
	protected boolean m_selectable;
	protected Object m_data;

	public ListData(Icon icon, int index, boolean selectable, Object data) {
		m_icon = icon;
		m_index = index;
		m_selectable = selectable;
		m_data = data;
	}

	public Icon getIcon() {
		return m_icon;
	}

	public int getIndex() {
		return m_index;
	}

	public boolean isSelectable() {
		return m_selectable;
	}

	public Object getObject() {
		return m_data;
	}

	public String toString() {
		return m_data.toString();
	}
}

// Acts as custom combo box list item renderer shows text with icon
class IconComboRenderer extends JLabel implements ListCellRenderer {
	private static final long serialVersionUID = 336617322964984503L;
	public static final int OFFSET = 16;
	protected Color m_textSelectionColor = Color.white;
	protected Color m_textNonSelectionColor = Color.black;
	protected Color m_textNonselectableColor = Color.gray;
	protected Color m_bkSelectionColor = new Color(0, 0, 128);
	protected Color m_bkNonSelectionColor = Color.white;
	protected Color m_borderSelectionColor = Color.yellow;
	protected Color m_textColor;
	protected Color m_bkColor;
	protected boolean m_hasFocus;
	protected Border[] m_borders;

	public IconComboRenderer() {
		super();
		m_textColor = m_textNonSelectionColor;
		m_bkColor = m_bkNonSelectionColor;
		m_borders = new Border[20];
		for (int k = 0; k < m_borders.length; k++)
			m_borders[k] = new EmptyBorder(0, OFFSET * k, 0, 0);
		setOpaque(false);
	}

	public Component getListCellRendererComponent(JList list, Object obj,
			int row, boolean sel, boolean hasFocus) {
		if (obj == null)
			return this;
		setText(obj.toString());
		boolean selectable = true;
		if (obj instanceof ListData) {
			ListData ldata = (ListData) obj;
			selectable = ldata.isSelectable();
			setIcon(ldata.getIcon());
			int index = 0;
			if (row >= 0) // No offset for editor (row=-1)
				index = ldata.getIndex();
			Border b = (index < m_borders.length ? m_borders[index]
					: new EmptyBorder(0, OFFSET * index, 0, 0));
			setBorder(b);
		} else
			setIcon(null);
		setFont(list.getFont());
		m_textColor = (sel ? m_textSelectionColor
				: (selectable ? m_textNonSelectionColor
						: m_textNonselectableColor));
		m_bkColor = (sel ? m_bkSelectionColor : m_bkNonSelectionColor);
		m_hasFocus = hasFocus;
		return this;
	}

	// Draws background excluding icon, and draws focus highlight
	public void paint(Graphics g) {
		Icon icon = getIcon();
		Border b = getBorder();
		g.setColor(m_bkNonSelectionColor);
		g.fillRect(0, 0, getWidth(), getHeight());
		g.setColor(m_bkColor);
		int offset = 0;

		if (icon != null && getText() != null) {
			Insets ins = getInsets();
			offset = ins.left + icon.getIconWidth() + getIconTextGap();
		}
		g.fillRect(offset, 0, getWidth() - 1 - offset, getHeight() - 1);
		if (m_hasFocus) {
			g.setColor(m_borderSelectionColor);
			g.drawRect(offset, 0, getWidth() - 1 - offset, getHeight() - 1);
		}
		setForeground(m_textColor);
		setBackground(m_bkColor);
		super.paint(g);
	}
}